Subject: fix for vulnerability CVE-2010-0401 for OpenTTD 0.6.0 - 0.6.3 (Access restriction circumvention, remote crash)
From: OpenTTD developer team <info@openttd.org>
Origin: backport, https://github.com/OpenTTD/OpenTTD/commit/11d6e21 https://github.com/OpenTTD/OpenTTD/commit/df4d8b3
Bug: https://github.com/OpenTTD/OpenTTD/issues/3754

It is possible to circumvent the server password of a network game. It is
possible in two cases:
  1.you know the company password of one of the companies
  1.one of the companies has no password

In both cases you send the company password (or “” if there is none) when you
receive the request for the server’s password.

This fix also prevents reading invalid data and possibly crashing a server when
a spectator sends a company password packet. Even though this is technically
a different vulnerability, the fix for the access restriction circumvention fixes
the other vulnerability too as it will never request and thus accept company
password packets from spectators.

Note that this is a custom fix for backports as a more elobarate fix with a
different network protocol was used for 1.0.1 and trunk (r19607). However,
we cannot break the network protocol for older versions thus this version is
needed.
This version does not change the network protocol, except for a
misbehaving client that sends the company password if the server password is
expected. In this case the client is disconnected citing a protocol error.


Index: src/console_cmds.cpp
===================================================================
--- src/console_cmds.cpp	(revision 19680)
+++ src/console_cmds.cpp	(working copy)
@@ -549,7 +549,8 @@
 {
 	static const char* const stat_str[] = {
 		"inactive",
-		"authorizing",
+		"authorizing (server password)",
+		"authorizing (company password)",
 		"authorized",
 		"waiting",
 		"loading map",
Index: src/network/core/tcp.h
===================================================================
--- src/network/core/tcp.h	(revision 19680)
+++ src/network/core/tcp.h	(working copy)
@@ -75,13 +75,15 @@
 /** Status of a client */
 enum ClientStatus {
 	STATUS_INACTIVE,   ///< The client is not connected nor active
-	STATUS_AUTHORIZING,///< The client is authorizing
+	STATUS_AUTH_GAME,  ///< The client is authorizing with game (server) password
+	STATUS_AUTH_COMPANY, ///< The client is authorizing with company password
 	STATUS_AUTH,       ///< The client is authorized
 	STATUS_MAP_WAIT,   ///< The client is waiting as someone else is downloading the map
 	STATUS_MAP,        ///< The client is downloading the map
 	STATUS_DONE_MAP,   ///< The client has downloaded the map
 	STATUS_PRE_ACTIVE, ///< The client is catching up the delayed frames
 	STATUS_ACTIVE,     ///< The client is an active player in the game
+	STATUS_END         ///< Must ALWAYS be on the end of this list!! (period)
 };

 /** Base socket handler for all TCP sockets */
Index: src/network/network_server.cpp
===================================================================
--- src/network/network_server.cpp	(revision 19680)
+++ src/network/network_server.cpp	(working copy)
@@ -221,7 +221,7 @@
 	/* Invalid packet when status is AUTH or higher */
 	if (cs->status >= STATUS_AUTH) return;

-	cs->status = STATUS_AUTHORIZING;
+	cs->status = type == NETWORK_GAME_PASSWORD ? STATUS_AUTH_GAME : STATUS_AUTH_COMPANY;

 	Packet *p = NetworkSend_Init(PACKET_SERVER_NEED_PASSWORD);
 	p->Send_uint8(type);
@@ -715,7 +715,7 @@
 	type = (NetworkPasswordType)p->Recv_uint8();
 	p->Recv_string(password, sizeof(password));

-	if (cs->status == STATUS_AUTHORIZING && type == NETWORK_GAME_PASSWORD) {
+	if (cs->status == STATUS_AUTH_GAME && type == NETWORK_GAME_PASSWORD) {
 		// Check game-password
 		if (strcmp(password, _network_game_info.server_password) != 0) {
 			// Password is invalid
@@ -733,7 +733,7 @@
 		// Valid password, allow user
 		SEND_COMMAND(PACKET_SERVER_WELCOME)(cs);
 		return;
-	} else if (cs->status == STATUS_AUTHORIZING && type == NETWORK_COMPANY_PASSWORD) {
+	} else if (cs->status == STATUS_AUTH_COMPANY && type == NETWORK_COMPANY_PASSWORD) {
 		ci = DEREF_CLIENT_INFO(cs);

 		if (strcmp(password, _network_player_info[ci->client_playas].password) != 0) {

